//
//  XPUncaughtExceptionHandler.m
//  PywCommonSDKDemo
//
//  Created by Ampaw on 2018/4/16.
//  Copyright © 2018年 Ampaw. All rights reserved.
//

#import "XPUncaughtExceptionHandler.h"
#include <libkern/OSAtomic.h>
#include <execinfo.h>
#import "XPCrashLogManager.h"

NSString * const XPUncaughtExceptionHandlerStackInfoKey = @"XPUncaughtExceptionHandlerStackInfoKey";
NSString * const XPUncaughtExceptionHandlerAppInfoKey = @"XPUncaughtExceptionHandlerAppInfoKey";

NSString * const XPUncaughtExceptionHandlerSignalExceptionName = @"XPUncaughtExceptionHandlerSignalExceptionName";
NSString * const XPUncaughtExceptionHandlerSignalKey = @"XPUncaughtExceptionHandlerSignalKey";

volatile int32_t XPUncaughtExceptionCount = 0;
const int32_t XPUncaughtExceptionMaximum = 10;

const NSInteger XPUncaughtExceptionHandlerSkipAddressCount = 4;
const NSInteger XPUncaughtExceptionHandlerReportAddressCount = 5;

@implementation XPUncaughtExceptionHandler

+ (NSArray *)backtrace
{
    void* callstack[128];
    int frames = backtrace(callstack, 128);
    char **strs = backtrace_symbols(callstack, frames);
    if (strs == NULL) {
        return nil;
    }
    
    int i;
    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
    for (i = XPUncaughtExceptionHandlerSkipAddressCount;
         i < XPUncaughtExceptionHandlerSkipAddressCount + XPUncaughtExceptionHandlerReportAddressCount;
         i++){
        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
    }
    free(strs);
    
    return backtrace;
}

- (void)handleException:(NSException *)exception
{
    // 存储Crash日志信息
    [[XPCrashLogManager manager] saveExceptionWithName:[exception name]
                                                Reason:[exception reason]
                                              UserInfo:[exception userInfo]];
    
    if ([[XPCrashLogManager manager] isOpenShowAlertView]) {
        NSString *message = [NSString stringWithFormat:NSLocalizedString(@"There is an exception in the application ！\n"
                                                                         @"%@\n%@", nil),[exception reason],[exception userInfo]];
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Unhandled exception", nil)
                                                        message:message
                                                       delegate:self
                                              cancelButtonTitle:NSLocalizedString(@"EXIT", nil)
                                              otherButtonTitles:nil, nil];
        [alert show];
        
        CFRunLoopRef runLoop = CFRunLoopGetCurrent();
        CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
        
        while (!xp_dismissed){
            for (NSString *mode in (__bridge NSArray *)allModes){
                CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
            }
        }
        
        CFRelease(allModes);
        
        NSSetUncaughtExceptionHandler(NULL);
        signal(SIGHUP,  SIG_DFL);
        signal(SIGINT,  SIG_DFL);
        signal(SIGQUIT, SIG_DFL);
        signal(SIGILL,  SIG_DFL);
        signal(SIGTRAP, SIG_DFL);
        signal(SIGABRT, SIG_DFL);
        signal(SIGEMT,  SIG_DFL);
        signal(SIGFPE,  SIG_DFL);
        signal(SIGKILL, SIG_DFL);
        signal(SIGBUS,  SIG_DFL);
        signal(SIGSEGV, SIG_DFL);
        signal(SIGSYS,  SIG_DFL);
        signal(SIGPIPE, SIG_DFL);
        signal(SIGALRM, SIG_DFL);
        signal(SIGTERM, SIG_DFL);
        signal(SIGURG,  SIG_DFL);
        signal(SIGSTOP, SIG_DFL);
        signal(SIGTSTP, SIG_DFL);
        signal(SIGCONT, SIG_DFL);
        signal(SIGCHLD, SIG_DFL);
        signal(SIGTTIN, SIG_DFL);
        signal(SIGTTOU, SIG_DFL);
        signal(SIGIO,   SIG_DFL);
        signal(SIGXCPU, SIG_DFL);
        signal(SIGXFSZ, SIG_DFL);
        signal(SIGVTALRM,SIG_DFL);
        signal(SIGPROF, SIG_DFL);
        signal(SIGWINCH,SIG_DFL);
        signal(SIGINFO, SIG_DFL);
        signal(SIGUSR1, SIG_DFL);
        signal(SIGUSR2, SIG_DFL);
        
        if ([[exception name] isEqual:XPUncaughtExceptionHandlerSignalExceptionName]){
            kill(getpid(), [[[exception userInfo] objectForKey:XPUncaughtExceptionHandlerSignalKey] intValue]);
        }else{
            [exception raise];
        }
    }
}

#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex
{
    xp_dismissed = YES;
    
    [self exitApplication];
}
/// 退出程序
- (void)exitApplication {
    UIWindow *window =  [UIApplication sharedApplication].keyWindow;
    [UIView animateWithDuration:1.0f animations:^{
        window.alpha = 0;
        window.frame = CGRectMake(0, window.bounds.size.width, 0, 0);
    } completion:^(BOOL finished) {
        exit(0);
    }];
}

@end

#pragma mark - C
NSString * getAppInfo(){
    NSString *bundle_id = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
    NSString *version = [NSString stringWithFormat:@"%@(%@)",[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];
    NSString *model =[UIDevice currentDevice].model;
    NSString *os_ver =[UIDevice currentDevice].systemVersion;
    NSString *appInfo = [NSString stringWithFormat:@"Bundle ID:%@  Application Version:%@  Equipment Model:%@  System Version:%@",bundle_id,version,model,os_ver];
    return appInfo;
}

NSString *signalString(int index){
    NSArray *signals = @[@"SIGHUP",@"SIGINT",@"SIGQUIT",@"SIGILL",@"SIGTRAP",
                         @"SIGABRT",@"SIGEMT",@"SIGFPE",@"SIGKILL",@"SIGBUS",
                         @"SIGSEGV",@"SIGSYS",@"SIGPIPE",@"SIGALRM",@"SIGTERM",
                         @"SIGURG",@"SIGSTOP",@"SIGTSTP",@"SIGCONT",@"SIGCHLD",
                         @"SIGTTIN",@"SIGTTOU",@"SIGIO",@"SIGXCPU",@"SIGXFSZ",
                         @"SIGVTALRM",@"SIGPROF",@"SIGWINCH",@"SIGINFO",@"SIGUSR1",@"SIGUSR2"];
    NSString *signalStr = @"";
    int signal_index = index - 1;
    if (signal_index < 0) {
        signalStr = @"SIG_NONE";
    }else{
        signalStr = [signals objectAtIndex:signal_index];
    }
    return signalStr;
}

void XPHandlerException(NSException *exception)
{
    int32_t exceptionCount = OSAtomicIncrement32(&XPUncaughtExceptionCount);
    if (exceptionCount > XPUncaughtExceptionMaximum){
        return;
    }
    
    // 异常名称
    NSString *name = [exception name];
    // 出现异常的原因
    NSString *reason = [exception reason];
    // 异常的堆栈信息【得到当前调用栈信息】
    NSArray *stackArray = [exception callStackSymbols];
    NSString *appInfo = getAppInfo();
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    [userInfo setObject:appInfo forKey:XPUncaughtExceptionHandlerAppInfoKey];
    [userInfo setObject:stackArray forKey:XPUncaughtExceptionHandlerStackInfoKey];
    
    NSException *exp = [NSException exceptionWithName:name
                                               reason:reason
                                             userInfo:userInfo];
    
    [[[XPUncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
                                                                withObject:exp
                                                             waitUntilDone:YES];
}

void XPMySignalHandler(int signal)
{
    int32_t exceptionCount = OSAtomicIncrement32(&XPUncaughtExceptionCount);
    if (exceptionCount > XPUncaughtExceptionMaximum){
        return;
    }
    NSString *reason = [NSString stringWithFormat:NSLocalizedString(@"Signal %@ was raised.", nil), signalString(signal)];
    
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:XPUncaughtExceptionHandlerSignalKey];
    NSArray *callStack = [XPUncaughtExceptionHandler backtrace];
    NSString *appInfo = getAppInfo();
    [userInfo setObject:appInfo forKey:XPUncaughtExceptionHandlerAppInfoKey];
    [userInfo setObject:callStack forKey:XPUncaughtExceptionHandlerStackInfoKey];
    
    NSException *exp = [NSException exceptionWithName:XPUncaughtExceptionHandlerSignalExceptionName
                                               reason:reason
                                             userInfo:userInfo];
    
    [[[XPUncaughtExceptionHandler alloc] init] performSelectorOnMainThread:@selector(handleException:)
                                                                withObject:exp
                                                             waitUntilDone:YES];
}

void XPInstallUncaughtExceptionHandler()
{
    NSSetUncaughtExceptionHandler(&XPHandlerException);
    signal(SIGHUP,  XPMySignalHandler); // 1     /* hangup */
    signal(SIGINT,  XPMySignalHandler); // 2     /* interrupt */
    signal(SIGQUIT, XPMySignalHandler); // 3     /* quit */
    signal(SIGILL,  XPMySignalHandler); // 4     /* illegal instruction (not reset when caught) */
    signal(SIGTRAP, XPMySignalHandler); // 5     /* trace trap (not reset when caught) */
    signal(SIGABRT, XPMySignalHandler); // 6     /* abort() */
    signal(SIGEMT,  XPMySignalHandler); // 7     /* EMT instruction */
    signal(SIGFPE,  XPMySignalHandler); // 8     /* floating point exception */
    signal(SIGKILL, XPMySignalHandler); // 9     /* kill (cannot be caught or ignored) */
    signal(SIGBUS,  XPMySignalHandler); // 10    /* bus error */
    signal(SIGSEGV, XPMySignalHandler); // 11    /* segmentation violation */
    signal(SIGSYS,  XPMySignalHandler); // 12    /* bad argument to system call */
    signal(SIGPIPE, XPMySignalHandler); // 13    /* write on a pipe with no one to read it */
    signal(SIGALRM, XPMySignalHandler); // 14    /* alarm clock */
    signal(SIGTERM, XPMySignalHandler); // 15    /* software termination signal from kill */
    signal(SIGURG,  XPMySignalHandler); // 16    /* urgent condition on IO channel */
    signal(SIGSTOP, XPMySignalHandler); // 17    /* sendable stop signal not from tty */
    signal(SIGTSTP, XPMySignalHandler); // 18    /* stop signal from tty */
    signal(SIGCONT, XPMySignalHandler); // 19    /* continue a stopped process */
    signal(SIGCHLD, XPMySignalHandler); // 20    /* to parent on child stop or exit */
    signal(SIGTTIN, XPMySignalHandler); // 21    /* to readers pgrp upon background tty read */
    signal(SIGTTOU, XPMySignalHandler); // 22    /* like TTIN for output if (tp->t_local&LTOSTOP) */
    signal(SIGIO,   XPMySignalHandler); // 23    /* input/output possible signal */
    signal(SIGXCPU, XPMySignalHandler); // 24    /* exceeded CPU time limit */
    signal(SIGXFSZ, XPMySignalHandler); // 25    /* exceeded file size limit */
    signal(SIGVTALRM,XPMySignalHandler);// 26    /* virtual time alarm */
    signal(SIGPROF, XPMySignalHandler); // 27    /* profiling time alarm */
    signal(SIGWINCH,XPMySignalHandler); // 28    /* window size changes */
    signal(SIGINFO, XPMySignalHandler); // 29    /* information request */
    signal(SIGUSR1, XPMySignalHandler); // 30    /* user defined signal 1 */
    signal(SIGUSR2, XPMySignalHandler); // 31    /* user defined signal 2 */
}
